//
//  ColdKnowledgeViewController.swift
//  Swift-Learning-Demo
//
//  Created by yuan xiaodong on 2022/6/15.
//  Copyright © 2022 yuan. All rights reserved.
//


import Foundation
import UIKit
import SnapKit
import ObjectMapper

private let cellIdentifier = "cellIdentifier"

class ColdKnowledgeViewController: BaseViewController{
    
    var didSetupConstraints = false
    
    override func viewDidLoad() {
        super.viewDidLoad()
        title = "冷知识"
        view.addSubview(self.tableView)
        view.setNeedsUpdateConstraints()
    }
    
    override func updateViewConstraints() {
        if !didSetupConstraints {
            tableView.snp_makeConstraints { (maker) in
                maker.edges.equalToSuperview()
            }
            didSetupConstraints = true
        }
        super.updateViewConstraints()
    }
    
    // MARK: - *********** 懒加载 ***********
    lazy var dataArr : NSArray = {
        let jsonData = dataForJson(fileName: "ColdKnowledge.json")
        let jsonStr = NSString(data: jsonData as Data, encoding: String.Encoding.utf8.rawValue)
        let dicArr = getArrayFromJSONString(jsonString: jsonStr! as String)
        
        //数组转模型
        var modelArr = [YSListModel]()
        for var item in dicArr{
            let model =  Mapper<YSListModel>().map(JSON: item as! [String : Any])
            modelArr.append(model!)
        }
        return modelArr as NSArray
    }()
    
    lazy var tableView : UITableView = {
        let table = UITableView.init()
        table.delegate = self;
        table.dataSource = self;
        table.tableFooterView = UIView()
        return table
    }()
    
    // MARK: - *********** 代理和Block尝试 ***********
    // MARK: -【1】mutating关键字
    /*
     Swift中protocol的功能比OC中强大很多，不仅能再class中实现，同时也适用于struct、enum。
     使用 mutating 关键字修饰方法是为了能在该方法中修改 struct 或是 enum 的变量，
     在设计接口的时候，也要考虑到使用者程序的扩展性。所以要多考虑使用mutating来修饰方法。
     */
    @objc func event1(){
//        let a = SimpleClass()
        var a = SimpleStruct()
//        var a = SimpleEnum.First
        a.adjust()
        let aDescription = a.simpleDescription
        print(aDescription)
    }
    
    // MARK: -【2】autoclosure的作用
    func logIfTrue(_ test: () ->Bool) {
        if test() {
            print("true")
        }
    }
    
    //@autoclosure的作用就是可以把一个表达式自动转换成闭包
    func logIfTrue(_ test: @autoclosure () ->Bool) {
        if test() {
            print("true")
        }
    }
    
    @objc func event2(){
        logIfTrue { () -> Bool in
            return true
        }
        logIfTrue({return true})
        logIfTrue{return true}
        logIfTrue(true)
    }
    
    // MARK: -【3】Optional类型
    @objc func event3(){
        let authorName: String? = "yuan"
        let authorAge: Int? = 23
        
        //1、强制拆包，在许多情况下安全
        //let age: Int = authorAge!
        //print("有年龄 \(age)")
        
        //隐式解包变量声明-在许多情况下安全，有风险，如果是nil直接崩溃
        //var age = ageInteger!
        
        //2、可选绑定 - 安全
        if let age = authorAge {
            print("有年龄 \(age)")
        }else{
            print("没有年龄")
        }
        
        //一次拆包多个值
        if let name: String = authorName,
           let age: Int = authorAge {
            print("作者是\(age)岁的\(name)。")
        } else {
            print("没有作者或没有年龄。")
        }
        
        //3、Nil 合并
        let age2 = authorAge ?? 0
        print("有年龄 \(age2)")
        
        //4、警卫模式
        guard let a = authorAge else {
            print("没有作者或没有年龄。")
            return
        }
        print("作者是\(a)岁。")
        
        //5、可选模式
        if case let a? = authorAge {
            print("作者是\(a)岁。")
        } else {
            print("没有作者或没有年龄。")
        }
    }
    
    // MARK: -【4】inout的作用
    /*
     1.使用 inout 关键字的函数，在调用时需要在该参数前加上 & 符号
     2.inout 参数在传入时必须为变量，不能为常量或字面量（literal）
     3.inout 参数不能有默认值，不能为可变参数
     4.inout 参数不等同于函数返回值，是一种使参数的作用域超出函数体的方式
     5.多个inout 参数不能同时传入同一个变量，因为拷入拷出的顺序不定，那么最终值也不能确定
     */
    @objc func event4(){
        var rect = Rectangle(width: 100, height: 100, origin: Point(x: -100, y: -100))
        print(rect.center)
        rect.reset(center: &rect.center)
        print(rect.center)
    }
    
    // MARK: -【5】struct 与 class 的区别
    /*
     1、 struct是值类型，class是引用类型：
     值类型的变量直接包含它们的数据，对于值类型都有它们自己的数据副本，因此对一个变量操作不可能影响另一个变量.值类型包括结构体 (数组和字典)，枚举，基本数据类型 (boolean, integer, float等).
     引用类型的变量存储对他们的数据引用,对一个变量操作可能影响另一个变量.
     二者的本质区别：struct是深拷贝；class是浅拷贝。

     2、 property的初始化不同：
     class 在初始化时不能直接把 property 放在默认的 constructor 的参数里，而是需要自己创建一个带参数的 constructor；而struct可以，把属性放在默认的 constructor 的参数里。

     3、 变量赋值方式不同：
     struct是值拷贝；class是引用拷贝。

     4、 immutable变量：
     swift的可变内容和不可变内容用var和let来甄别，如果初始为let的变量再去修改会发生编译错误。struct遵循这一特性；class不存在这样的问题。

     5、 mutating function：
     struct 和 class 的差別是 struct 的 function 要去改变 property 的值的时候要加上 mutating，而 class 不用。

     6、 继承：
     struct不可以继承，class可以继承。

     7、 struct比class更轻量：
     struct分配在栈中，class分配在堆中。
     */
    @objc func event5(){
        
    }
    
    // MARK: -【6】closure 与OC中block的区别
    /*
     1、closure是匿名函数、block是一个结构体对象
     2、closure通过逃逸闭包来在内部修改变量，block 通过 __block 修饰符
     */
    @objc func event6(){
        handleData { a in
            print(a)
        }
        
        getData { a in
            print(a)
        }
    }
    
    //非逃逸闭包，函数执行完之前，闭包就执行完了，闭包没有逃出函数的作用域
    func handleData(closure:(Any) -> Void) {
        print("函数开始执行 --- \(Thread.current)")
        print("函数执行了闭包 --- \(Thread.current)")
        closure("456")
        print("函数执行结束 --- \(Thread.current)")
    }
    
    //逃逸闭包--函数执行完后，闭包里面的内容还可以执行，闭包作用域逃出了函数
    func getData(closure: @escaping (Any) -> Void){
        print("函数开始执行 --- \(Thread.current)")
        DispatchQueue.global().async {
            DispatchQueue.main.asyncAfter(deadline: DispatchTime.now()+2, execute: {
                print("函数执行了闭包 --- \(Thread.current)")
                closure("456")
            })
        }
        print("函数执行结束 --- \(Thread.current)")
    }
    
    // MARK: -【7】KVC和KVO
    @objc func event7(){
        //KVC需要继承NSObject
        let kvc = KVCClass()
        kvc.show()
        kvc.setValue("456", forKey: "someValue")
        kvc.show()
        
        //KVO
        //由于 Swift 为了效率, 默认禁用了动态派发, 因此 Swift 要实现 KVO, 除了要继承自 NSObject 外还要将观测的对象标记为 dynamic(让 swift 代码也能有 Objective-C 中的动态机制).
        let kvoclass = KVOClass()//被观察者
        let observer = MyObserver(object: kvoclass)
        kvoclass.updateValue()
    }
    
    // MARK: -【8】associatedtype和typealias
    @objc func event8(){
        let p: People2<Apple> = People2()
        p.eat(food: Apple())
        
        let b: People2<Orange> = People2()
        b.eat(food: Orange())
    }
    
    //typealias
    //1、可以用来对已有的类型进行重命名，比如在表示两点之间的距离的时候，可以使用typealias将x和y轴的距离Double表示为Distance。
    typealias Distance = Double
    func distance2(p1: CGPoint, p2: CGPoint) -> Double {
        let x = Distance(p1.x - p2.x)
        let y = Distance(p1.y - p2.y)
        return sqrt(x * x + y * y)
    }
    //2、可以对闭包进行重新命名，这样在做参数传递的时候更加清晰
    typealias Success = (_ result: String) -> Void
    typealias Failure = (_ error: String) -> Void
    public func excuteNetworking(_ successBlock: Success, failBlock: Failure) {
        
    }
    
    //associatedtype
    //1、关联类型作为协议实现泛型的一种方式，可以在协议中预先定义一个占位符，实现协议的时候再确定这个占位符具体的类型。
    
    // MARK: -【9】defer、guard的作用
    /*
     defer 语句块中的代码, 会在当前作用域结束前调用,无论函数是否会抛出错误。每当一个作用域结束就进行该作用域defer执行。 如果有多个 defer, 那么后加入的先执行.
     guard 是一个要求表达式的值为 true 从而继续执行的条件语句。如果表达式为 false，则会执行必须提供的 else 分支
     */
    @objc func event9(){
        
    }
    
    enum StoryError:Error {
        case missing
        case illegible
        case tooScary
    }
    
    //未使用guard的函数
    func readBedtimeStory() throws {
        if let url = Bundle.main.url(forResource: "books", withExtension: "txt") {
            
            if let data = try? Data(contentsOf: url), let story = String(data: data, encoding: .utf8) {
                if story.contains("👾") {
                    throw StoryError.tooScary
                } else {
                    print("Once upon a time...\(story)")
                }
            } else {
                throw StoryError.illegible
            }
            
        } else {
            throw StoryError.missing
        }
    }
    
    //使用 guard 语句组织代码可以让代码读起来更加的线性：
    func readBedTimeStory2() throws {
        
        guard let url = Bundle.main.url(forResource: "books", withExtension: "txt") else {
            throw StoryError.missing
        }
        
        guard let data = try? Data(contentsOf: url), let story = String(data: data, encoding: .utf8) else {
            throw StoryError.illegible
        }
        
        if story.contains("😈") {
            throw StoryError.tooScary
        }
        
        print("Once upon a time...\(story)")
        
    }
    
    func currentHostName2() -> String {
        let capacity = Int(NI_MAXHOST)
        let buffer = UnsafeMutablePointer<Int8>.allocate(capacity: capacity)
        defer {
            buffer.deallocate()
            //尽管 defer 紧接着出现在 allocate(capacity:) 调用之后，但它要等到当前区域结束时才会被执行。多亏了 defer，buffer 才能无论在哪个点退出函数都可以被释放。
            //考虑在任何需要配对调用的 API 上都使用 defer，比如 allocate(capacity:) / deallocate()、wait() / signal() 和 open() / close()。
        }
        
        guard gethostname(buffer, capacity) == 0 else {
            return "localhost"
        }
        
        return String(cString: buffer)
    }
    
    // MARK: -【10】给集合中元素是字符串的类型增加一个扩展方法，应该怎么声明
    @objc func event10(){
        let a = ["1","2"].isStringElement
        print(a)
    }
    
    // MARK: -【11】一个函数的参数类型只要是数字（Int、Float）都可以，要怎么表示
    @objc func event11(){
        myMethod(1)
        myMethod(0.3)
    }
    
    func myMethod<T>(_ value:T) where T: Numeric {
        print(value + 1)
    }
    
    // MARK: -【12】一个类型表示选项，可以同时表示有几个选项选中（类似 UIViewAnimationOptions ），用什么类型表示
    @objc func event12(){
        let options: SomeOption = [.option1,.option2]
        if options.contains(.option1) {
            print("包含option1")
        }
        
        if options.contains(.option2) {
            print("包含option2")
        }
    }
    
    // MARK: -【13】@objc关键字
    @objc func event13(){
        //@objc 作用
        //1 fileprivate 或者 private  保证方法私有 能在同一个类 或者 同一个文件（extension）中访问这个方法 如果定义为private  那么只能在一个类中访问 不能在类扩展中访问
        //2 允许这个函数在“运行时”通过oc的消息机制调用
    }
    
}

struct SomeOption: OptionSet {
    let rawValue: Int
    static let option1 = SomeOption(rawValue: 1 << 0)
    static let option2 = SomeOption(rawValue: 1 << 1)
    static let option3 = SomeOption(rawValue: 1 << 2)
}

//使用where子句，限制Element为String
extension Array where Element == String {
    var isStringElement:Bool {
        return true
    }
}

// MARK: - *********** Models ***********

protocol Food {
    func desc() -> String
}

class Apple: Food {
    func desc() -> String {
        return "apple"
    }
}

class Orange: Food {
    func desc() -> String {
        return "orange"
    }
}

protocol Animal {
    func eat(food: Food)
}

class Person: Animal {
    func eat(food: Food) {
        print("eat \(food.desc())")
    }
}

protocol Animal2 {
    associatedtype F: Food
    func eat(food: F)
}

class People2<F: Food>: Animal2 {
    typealias MF = F
    
    func eat(food: MF) {
        print("eat \(food.desc())")
    }
}

struct Point {
    var x = 0.0
    var y = 0.0
}

struct Rectangle {
    var width = 0.0
    var height = 0.0
    var origin = Point()
    
    var center: Point {
        get {
            print("center GETTER call")
            return Point(x: origin.x + width / 2,
                         y: origin.y + height / 2)
        }
        
        set {
            print("center SETTER call")
            origin.x = newValue.x - width / 2
            origin.y = newValue.y - height / 2
        }
    }
    
    func reset(center: inout Point) {
        center.x = 0.0
        center.y = 0.0
    }
    
}

class KVCClass: NSObject {
    @objc var someValue: String = "123"
    func show() {
        print("someValue的值：\(someValue)")
    }
}

class KVOClass: NSObject {
    @objc dynamic var someValue = "abc"
    func updateValue() {
        someValue = "bbc"
    }
}

class MyObserver: NSObject {
    @objc var kvoClass: KVOClass
    var observation: NSKeyValueObservation?
    
    init(object: KVOClass) {
        kvoClass = object
        super.init();
        observation = observe(\.kvoClass.someValue, options: [.old,.new], changeHandler: { object , change in
            print("someValue changed from: \(change.oldValue!), update to:\(change.newValue!)")
        })
    }
}

// MARK: - *********** UITableViewDelegate ***********

extension ColdKnowledgeViewController:UITableViewDelegate,UITableViewDataSource{
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return self.dataArr.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        
        var cell: YSTableViewCell? = nil
    
        if cell != nil {
            cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as? YSTableViewCell
        }else{
            cell = YSTableViewCell.init(style: UITableViewCell.CellStyle.subtitle, reuseIdentifier: cellIdentifier)
        }
        
        let model = self.dataArr[indexPath.row] as? YSListModel
        cell?.model = model
        cell?.textLabel?.text = model?.title
        cell?.detailTextLabel?.text = model?.subTitle
        return cell!
    }
    
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        tableView.deselectRow(at: indexPath, animated: true)
        let model = self.dataArr[indexPath.row] as? YSListModel
        let selector = Selector(model!.event)
        
        if self.responds(to:selector) {
            self.perform(selector)
        }else{
            print("\(selector.description)方法不存在！！！")
        }
    }
}
